/**
 * @author rdru
 * INV: 
 */
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Vector;

import server.ActiveDoc;

import abstractions.Atom;
import abstractions.AtomId;
import abstractions.Command;
import abstractions.CommandC;
import abstractions.LoggedUser;
import abstractions.OpenDoc;
import abstractions.Part;
import abstractions.Pos;
import abstractions.Selection;
import static server.ServerMonitor.*;
import emu.EMU;
import interfaces.EMUW;

public class SC2 {
	class User {
		String	logname;
		String	email;
		String	name;
	}

	class LoggedUser extends User {
		int id;
		int clientId;
		volatile HashMap<Integer, ActiveDoc> edits;		// <docId, Doc>
	}

	class Doc {
		protected String	fileName;
		String				pathName;
		boolean				isRemote;
	}

	class OpenDoc extends Doc {			// used by the Client
		int								id;			// docId
		String							text;
		HashMap<Integer, User>			editedBy;	// <userId, Usr>
		Vector<LinkedList<CommandC>>	histOld;
		HashMap<AtomId, Integer>		locked;		// <AtomId, userId>
		int								ups;		// # updates sent
		int								upr;		// # updates recieved
		int								n;			// total # Commands recieved
	}

	class ActiveDoc extends OpenDoc {	// used by the Server
		LinkedList<CommandC>			histNew;
	}
	
	class EditingDoc extends OpenDoc {	// used by the Client
		LinkedList<Atom>				atoms;		// the representation of a doc
		Pos								cp;			// current position
		Selection						cs;			// current selection
		// Part							cb;			// clipboard
		public boolean					isDirty;	// state info
		LinkedList<Command>				histNew;
		LinkedList<Command>				undo;
		LinkedList<EMUW>				winList;	// where this EMUDoc is shown
		HashMap<AtomId, Atom>			id2atom;	// HashMap to speed AtomId to Atom
	}


	class Server {
		HashMap<String, Doc>			regDocs;				// docFullPath, doc
		HashMap<String, User>			regUsers;				// logname, User

		HashMap<Integer, ActiveDoc>		activeDocs;				// docId, Doc
		HashMap<String, Integer>		openDocs;				// docFullPath, docId
		HashMap<Integer, LoggedUser>	loggedUsers;			// userId, User
		HashMap<Integer, String>		passwords;				// passwords of reg users
		HashMap<Integer, String>		clients;				// clientId, IP
		int								myServerId		= 0;
		int								nextUserId		= 0;
		int								nextClientId	= 0;

		// internal methods
		LoggedUser validateClient(int clientId, int userId) {
			LoggedUser u;
			if (! validateClient(clientId)
			    || (u = loggedUsers.get(userId)) == null)
				return null;
			return u;
		}

		boolean validateClient(int clientId) {
			return clients.containsKey(clientId);
		}

		boolean validateUser(String logname, String password) {
			return password.equals(passwords.get(logname));
		}

		Doc openFile(String docPath) {
			Doc d = null;
			return d;
		}

		boolean saveCloseFile(int docId) {
			return true;
		}
		
		boolean docAdd(int clientId, int userId, String docName, String docText) {
			logDocAdd(userId, docName);
			if (validateClient(clientId, userId) == null) {
				serverLogAdd(docAdd.isSelected(), ": Failed validate clientId");
				return false;
			}
			// add doc to regDocs and create a new file for the new doc...
			return true;
		}

		boolean docRemove(int clientId, int userId, String docName) {
			logDocRemove(userId, docName);
			if (validateClient(clientId, userId) == null) {
				serverLogAdd(docRemove.isSelected(), " Failed validate clientId or userId!!");
				return false;
			}

			// do not know how to handle this yet ...
			return true;
		}

		int registerClient(String IP) {
			clients.put(nextClientId++, IP);
			return nextClientId - 1;
		}

		void removeClient(int clientId) {
			validateClient(clientId);
			clients.remove(clientId);
		}

		
		// methods available to the clients
		int login(int clientId, String logname, String password) {
			logUserLogin(clientId, logname);
			if (! validateClient(clientId))
				return -1;
			if (! validateUser(logname, password)) {
				serverLogAdd(userLogin.isSelected(), " Failed wrong logname/pasword!!");
				return -1;
			}
			LoggedUser u = (LoggedUser) regUsers.get(logname);
			if (loggedUsers.containsValue(u)) {
				serverLogAdd(userLogin.isSelected(), " Failed already logged!!");
				return -1;
			}
			loggedUsers.put(nextUserId + 1, u);
			return nextUserId++;
		}

		boolean logout(int clientId, int userId) {
			logUserLogout(userId);
			LoggedUser u;
			if ((u = (LoggedUser) validateClient(clientId, userId)) == null) {
				serverLogAdd(userLogout.isSelected(), " Failed validate clientId/userId");
				return false;
			}
			if (!u.edits.isEmpty()) {
				serverLogAdd(userLogout.isSelected(), " Failed needs to close open docs");
				return false;
			}
			loggedUsers.remove(userId);
			return true;
		}

		public OpenDoc docOpen(int clientId, int userId, String docPath) {
		logDocOpen(userId, docPath);
		LoggedUser u;
		if ((u = validateClient(clientId, userId)) == null) {
			serverLogAdd(docOpen.isSelected(), " Failed validate clientId/userId");
			return null;
		}
		
		// 
		ActiveDoc d = (ActiveDoc) regDocs.get(docPath);
		if (d == null) {
			serverLogAdd(docOpen.isSelected(), " Failed doc does not exist!!");
			return null;
		}
		
		// if doc is not open, open it
		if (!activeDocs.containsKey(d.id))
			d = (ActiveDoc) openFile(docPath); // opens and add to activeDocs
		// add user to editedBy
		d.editedBy.put(userId, u);
		return d;
	}
	
		public void docClose(int clientId, int userId, int docId) {
			logDocClose(userId, docId);
			LoggedUser u;
			if ((u = validateClient(clientId, userId)) == null) {
				serverLogAdd(docClose.isSelected(), " Failed validate clientId/userId");
				return;
			}
			ActiveDoc d =  activeDocs.get(docId);
			d.upr = d.histOld.size() + d.histNew.size();	// updates received
			d.ups = d.histOld.size();						// updates sent
			d.n = 0;										// total # commands received
			for (LinkedList<CommandC> c: d.histOld)
				d.n += c.size();
			d.n += d.histNew.size();
			if ((d = activeDocs.get(docId)) == null) {
				serverLogAdd(docClose.isSelected(), " Failed doc does not exist!!");
				return;
			}
			d.editedBy.remove(docId);
			u.edits.remove(docId);
			if (d.editedBy.isEmpty()) {
				// update and save doc to file...
				activeDocs.remove(docId);
			}
			serverLogAdd(docClose.isSelected(), ", received: " + d.upr
			             + " sent: " + d.ups + " comands:" + d.n);
		}

		public boolean lockRequest(int clientId, int userId, int docId, AtomId aId) {
			logLockRequest(userId, docId, aId);
			if (validateClient(clientId, userId) == null) {
				serverLogAdd(lockRequest.isSelected(), " Failed validate clientId/userId");
				return false;
			}
			ActiveDoc d = activeDocs.get(docId);
			if (d.locked.containsKey(aId)) {
				logLockDeny(userId, docId, aId);
				return false;
			} else {
				logLockGrant(userId, docId, aId);
				d.locked.put(aId, userId);
				return true;
			}
		}

		public boolean lockRelease(int clientId, int userId, int docId, AtomId aId) {
			logLockRelease(userId, docId, aId);
			if (validateClient(clientId, userId) == null) {
				serverLogAdd(lockRelease.isSelected(), " Failed validate clientId/userId");
				return false;
			}
			ActiveDoc d = activeDocs.get(docId);
			int uId = d.locked.get(aId);
			if (uId == -1 || uId != userId) {
				serverLogAdd(lockGrant.isSelected(), " Failed non-existent lock or locked to other user");
				return false;
			}
			d.locked.remove(aId);
			return true;
		}

		boolean updateDoc(int clientId, int userId, int docId, LinkedList<CommandC> cmds) {
		logRecieved(userId, docId, cmds.size());
		LoggedUser u = validateClient(clientId, userId);
		if (u == null) {
			serverLogAdd(received.isSelected(), " Failed validate clientId/userId");
			return false;
		}
		ActiveDoc d = u.edits.get(docId);
		if (d != null) {
			d.histNew.addAll(cmds);
			return true;
		} else {
			serverLogAdd(received.isSelected(), " Failed: user does not have this docId open");
			return false;
		}
	}

		
		// prepare to send updates to clients
		synchronized boolean send(int docId) {
			logSent(docId);
			ActiveDoc d = activeDocs.get(docId);
			if (d.histNew.size() > 0) {
				// forall users editing d do:
				{
					updateDoc(myServerId, docId, d.histNew);
				}
				d.histOld.addAll(d.histNew);
				return true;
			}
			serverLogAdd(sent.isSelected(), " Failed: user does not have this docId open");
			return false;
		}

	}

	class Client {
		int clientId;
		int myServerId;

		// windows where the editing docs are viewed
		HashMap<Integer, EMUW>			viewedIn;			// <docId, EMUW>
		HashMap<Integer, EditingDoc>	editingDocs;		// docId, Doc
		HashMap<Integer, LoggedUser>	coopUsers;			// userId, User
		HashMap<Integer, Server>		servers	;			// serverId, Server
		int								serverFreq = 1;

		boolean validateServer(int serverId) {
			return servers.containsKey(serverId);
		}

		// EMUDoc
		boolean lockAtom(int serverId, int docId, AtomId aId, int userId) {
			EMU.cw.logLockAtom(docId, aId, userId);
			if (! validateServer(serverId)) {
				EMU.cw.status.append(" Failed validate server");
				return false;
			}
			EditingDoc d = editingDocs.get(docId);
			if (d == null) {
				EMU.cw.status.append(" Failed docId is not open by this client");
				return false;
			}
			d.locked.put(aId, userId);
			return true;
		}

		boolean unlockAtom(int serverId, int docId, AtomId aId) {
			EMU.cw.logUnlockAtom(docId, aId, userId);
			if (! validateServer(serverId)) {
				EMU.cw.status.append(" Failed validate server");
				return false;
			}
			EditingDoc d = editingDocs.get(docId);
			if (d == null) {
				EMU.cw.status.append(" Failed docId is not open by this client");
				return false;
			}
			d.locked.remove(aId);
			return true;
		}

		boolean addClientToDoc(int serverId, int docId, int userId) {
			EMU.cw.logAddClientToDoc(userId, docId);
			if (! validateServer(serverId)) {
				EMU.cw.status.append(" Failed validate server");
				return false;
			}
			EditingDoc d = editingDocs.get(docId);
			if (d == null) {
				EMU.cw.status.append(" Failed docId is not open by this client");
				return false;
			}
			LoggedUser u = coopUsers.get(userId);
			if (u == null)
				coopUsers.put(userId, u);
			d.editedBy.put(userId, u);
			u.edits.put(docId, d);
			return true;
		}

		boolean removeClientFromDoc(int serverId, int docId, int userId) {
			EMU.cw.logRemoveClientFromDoc(userId, docId);
			if (! validateServer(serverId)) {
				EMU.cw.status.append(" Failed validate server");
				return false;
			}
			EditingDoc d = editingDocs.get(docId);
			if (d == null) {
				EMU.cw.status.append(" Failed docId is not open by this client");
				return false;
			}
			LoggedUser u = coopUsers.get(userId);
			if (u == null) {
				EMU.cw.status.append(" Failed User not editing this Doc");
				return false;
			}
			d.editedBy.remove(userId);
			u.edits.remove(docId);
			return true;
		}

		// EMU
		boolean updateDoc(int serverId, int docId, LinkedList<CommandC> cmds) {
			EMU.cw.logUpdateDoc(docId);
			if (! validateServer(serverId)) {
				EMU.cw.status.append(" Failed validate server");
				return false;
			}
			EditingDoc d = editingDocs.get(docId);
			if (d == null) {
				EMU.cw.status.append(" Failed User not editing this Doc");
				return false;
			}
			// execute and insert cmds into histOld (as many threads as possible)
			//executeCommands(d, cmds);
			return true;
		}

		void updateServerFreq(int serverId, int freq) {
			validateServer(serverId);
			serverFreq = freq;
		}
	}
}

